package validate;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public final class PasswordHasher {
	public static final String ALGORITMO = "HmacSHA512";
	public static final int LENGTH_DE_SALT = 64;
	public static final int ITERACIONES = 1000;
	public static final String CHARSET_NAME = "UTF-8";

	public static class PasswordHash {
		private final byte[] hash;
		private final byte[] salt;

		public PasswordHash(final byte[] hash, final byte[] salt) {
			this.hash = hash;
			this.salt = salt;
		}
		public byte[] getHash() {
			return hash;
		}
		public byte[] getSalt() {
			return salt;
		}
	}
	private PasswordHasher() {
		super();
	}

	/**
	 * genera un valor de salt aleatorio y lo utiliza
	 * para calcular repetitivamente tantas operaciones de <code>hash</code>
	 * 
	 * @param value: el valor para el que calcular
	 */
	public static PasswordHash hash(final String value) {

		final byte[] salt = new byte[LENGTH_DE_SALT];
		final SecureRandom rnd = new SecureRandom();
		rnd.nextBytes(salt);
		return hash(value, salt);
	}

	/**
	 * Dado un Salt y el Valor calcula el hash.
	 * 
	 */
	public static PasswordHash hash(final String valor, final byte[] salt) {
		try {
			byte[] retVal;
			final byte[] valueBytes = valor.getBytes(CHARSET_NAME);
			final Mac mac = Mac.getInstance(ALGORITMO);
			final Key key = new SecretKeySpec(salt, ALGORITMO);
			mac.init(key);
			retVal = mac.doFinal(valueBytes);
			for (int i = 1; i < ITERACIONES; i++) {
				retVal = mac.doFinal(retVal);
			}
			return new PasswordHash(retVal, salt);
		} catch (final NoSuchAlgorithmException cause) {
			throw new RuntimeException(cause);
		} catch (final InvalidKeyException cause) {
			throw new RuntimeException(cause);
		} catch (final UnsupportedEncodingException cause) {
			throw new RuntimeException(cause);
		}
	}
	/**
	 * funcion para validar una clave
	 * 
	 * @param value : clave a verificar
	 * @param correctHash : es el hash resultado de la clave correcta
	 * @param salt : es el salt con el que se calculo la clave correcta.
	 * @return
	 */
	public static boolean validar(final String value, final byte[] correctHash,
			final byte[] salt) {
		final PasswordHash ph = hash(value, salt);
		return Arrays.equals(ph.getHash(), correctHash);
	}
}
